package com.martin.lib_base.net.interceptor

import com.google.gson.Gson
import com.google.gson.JsonObject
import com.martin.lib_base.BuildConfig
import com.martin.lib_base.constant.ConstantKey
import com.martin.lib_base.ktx.toJson
import dev.utils.common.RandomUtils
import dev.utils.common.cipher.Base64
import dev.utils.common.encrypt.MD5Utils
import okhttp3.*
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.ResponseBody.Companion.toResponseBody
import okio.ByteString.Companion.decodeBase64
import okio.internal.commonToUtf8String
import org.json.JSONObject
import java.nio.charset.StandardCharsets
import javax.crypto.Cipher
import javax.crypto.Mac
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec

class SignAndEncryptionInterceptor : Interceptor {

    //签名 Key
    private val _signKey = if (BuildConfig.ENCRYPT_TYPE == ConstantKey.RELEASE)
        BuildConfig.APP_SIGN_KEY
    else
        BuildConfig.APP_SIGN_KEY_TEST

    //AES 加密Key
    private val _aesKey = if (BuildConfig.ENCRYPT_TYPE == ConstantKey.RELEASE)
        BuildConfig.APP_AES_KEY
    else
        BuildConfig.APP_AES_KEY_TEST

    //处理后的AESKey
    private val aesKey = Base64.decode(_aesKey.substring(8, _aesKey.length - 8), Base64.DEFAULT)

    /**
     * 是否需要签名并加密
     * 否则只需要签名
     */
    private val needSignAndEncryption = BuildConfig.env == ConstantKey.RELEASE

    //不参与生成签名的字段
    private val _excludeSignKey = listOf("data")

    //加密后的数据Key
    private val _encryptDataKey = "encryptedData"

    //JSON解析器
    private val _gson = Gson()

    /**
     * 拦截处理
     */
    override fun intercept(chain: Interceptor.Chain): Response {
        // 对请求进行加密,生成加密请求
        val request = signAndEncryptRequest(chain.request())
        // 发起加密请求
        val response = chain.proceed(request)

        //是否有返回体,且为签名加密类型
        if (response.body != null && needSignAndEncryption) {
            return decryptResponse(response)
        }

        return response
    }

    /**
     * 解密返回参数
     */
    private fun decryptResponse(response: Response): Response {
        val body = response.body!!
        try {
            body.source().request(Long.MAX_VALUE)
            val buffer = body.source().buffer
            //获取接口数据
            val bodySource = buffer.clone().readUtf8()
            showLog("处理前未格式化 $bodySource")

            showLog("处理前的数据 ${JSONObject(bodySource).toString(2)}")
            //获取服务端返回的数据模型
            val serviceResponse =
                _gson.fromJson(bodySource, JsonObject::class.java)

            //获取加密的数据
            val encryptData = serviceResponse.get(_encryptDataKey).asString
            //解密
            val json = doAESDecrypt(encryptData)
            showLog("解密后数据 : $json")

            return response.newBuilder()
                .body(json.toResponseBody(body.contentType()))
                .build()
        } catch (e: Exception) {
            showLog("解码失败 : ${e.message}")
            return response
        }
    }

    /**
     * 签名并加密请求
     * 默认只有POST请求，如果后面新增其他类型，需要额外增加逻辑
     */
    private fun signAndEncryptRequest(request: Request): Request {
        if (request.method != "POST"
            || request.body == null
            || request.body?.contentType()?.toString()
                ?.contains(MultipartBody.FORM.toString()) == true
        ) {
            return request
        }
        val body = request.body!!
        //将请求参数转为JsonObject
        val jsonObject = getJsonObjectFromRequestBody(body)
        //获取时间戳
        val timestamp = System.currentTimeMillis() / 1000
        //将时间戳添加到参数中
        jsonObject.addProperty("timestamp", timestamp)
        //生成签名
        val sign = generateSign(jsonObject)
        //添加签名参数
        jsonObject.addProperty("sign", sign)
        showLog("签名后的数据  ${jsonObject.toJson()}")
        //生成新的请求体
        val requestBody = if (needSignAndEncryption) {
            //对Json对象进行加密,生成密文
            val aesEncrypt = doAESEncrypt(jsonObject)
            showLog("加密生成的字符串  $aesEncrypt")
            mapOf(
                _encryptDataKey to aesEncrypt
            ).toJson(false)
        } else {
            jsonObject.toJson(false)
        }.toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull())

        //返回新的请求
        return request.newBuilder()
            .post(requestBody)
            .build()
    }

    /**
     * 执行AES解密
     */
    private fun doAESDecrypt(encryptData: String): String {
        showLog("Base64解码前数据  $encryptData")
        val data = Base64.decode(encryptData, Base64.DEFAULT).commonToUtf8String()
        showLog("Base64解码后数据  $data")
        val dataJson = _gson.fromJson(data, JsonObject::class.java)
        //偏移数据
        val iv = dataJson.get("iv").asString.decodeBase64()!!.toByteArray()
        showLog("获取iv bytes : ${String(iv, StandardCharsets.UTF_8)}")
        //待解密数据
        val decryptData = dataJson.get("value").asString.decodeBase64()!!
        //生成AES解码器
        val cipher = generateAESCipher(iv, false)
        //返回解码结果
        return cipher.doFinal(decryptData.toByteArray())
            .commonToUtf8String()
    }

    /**
     * 做AES加密
     */
    private fun doAESEncrypt(jsonObject: JsonObject): String {
        //加工生成加密信息
        //生成随机字符串
        val iv = RandomUtils.getRandomNumbersAndLetters(16).toByteArray()
        //获取AES加密器
        val cipher = generateAESCipher(iv)
        //数据加密
        val doFinal = cipher.doFinal(jsonObject.toJson(false).toByteArray())
        //转Base64
        val cipherText = Base64.encodeToString(doFinal, Base64.NO_WRAP)
        val ivStr = Base64.encodeToString(iv, Base64.NO_WRAP)
        val map = mapOf(
            "iv" to ivStr,
            "value" to cipherText,
            "mac" to generateMac(ivStr, cipherText),
        )
        showLog("加密获取的 map  ${map.toJson()}")
        return Base64.encodeToString(_gson.toJson(map).toByteArray(), Base64.NO_WRAP)
    }

    /**
     * 生成AES密码
     * @param isEncrypt 是否为加密模式,反之为解密
     */
    private fun generateAESCipher(iv: ByteArray, isEncrypt: Boolean = true): Cipher {
        //获取AES加密器
        val cipher = Cipher.getInstance("AES/CBC/PKCS7Padding")
        //初始化秘钥
        cipher.init(
            if (isEncrypt) Cipher.ENCRYPT_MODE else Cipher.DECRYPT_MODE,
            SecretKeySpec(aesKey, "AES"),
            IvParameterSpec(iv)
        )
        return cipher
    }

    /**
     * 生成签名
     */
    private fun generateSign(jsonObject: JsonObject): String {
        val sb = StringBuilder()
        jsonObject.keySet()
            //除`_excludeSignKey`外的字段
            .filter { !_excludeSignKey.contains(it) }
            //通过ASCII码排序
            .sortedBy {
                it.toCharArray().first().lowercase()
            }
            //以key=value形式拼接
            .forEach {
                if (sb.isNotEmpty()) {
                    sb.append("&")
                }
                sb.append(it)
                    .append("=")
                    .append(jsonObject.get(it))
            }

        //前后拼接签名Key
        sb.insert(0, _signKey)
        sb.append(_signKey)

        //整理字符串
        val signBase = sb.toString()
            .trim()
            .replace(" ", "")
            .replace("\"", "")
        showLog("拼接后的 signBase 字符串 $signBase")

        //生成Base64签名
        val sign64 = Base64.encodeToString(signBase.toByteArray(), android.util.Base64.NO_WRAP)
        showLog("签名后的 sign64 字符串 $sign64")

        //生成MD5签名
        val sign = MD5Utils.md5(sign64).lowercase()
        showLog("签名后的 sign 字符串 $sign")
        return sign
    }

    /**
     * 获取[JsonObject] 根据[RequestBody]生成
     */
    private fun getJsonObjectFromRequestBody(body: RequestBody): JsonObject {
        return _gson.fromJson(okio.Buffer().apply {
            body.writeTo(this)
        }.readUtf8(), JsonObject::class.java)
    }

    /**
     * 生成mac信息
     */
    private fun generateMac(iv: String, cipherText: String): String {
        val content = "${iv}${cipherText}".toByteArray()
        val algorithm = "HmacSHA256"
        val final = Mac.getInstance(algorithm).apply {
            init(SecretKeySpec(aesKey, algorithm))
            update(content)
        }.doFinal()
        return hashToHex(final)
    }

    /**
     * hash转16进制字符串
     */
    private fun hashToHex(hash: ByteArray): String {
        val sb = StringBuilder()
        for (i in hash.indices) {
            sb.append(String.format("%02x", hash[i]))
        }
        return sb.toString()
    }

    private fun showLog(message: String) {
//        loge("加解密拦截器 : $message")
    }
}